package com.gitee.chili.leisure.server;


import com.gitee.chili.leisure.properties.InitializeNettyProperties;
import com.gitee.chili.leisure.properties.NettyHandlerProperties;
import com.gitee.chili.leisure.util.RemoteUtil;
import com.gitee.chili.leisure.util.SimpleThreadFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.util.Objects;

/**
 * AbstractBootstrapServer
 *
 * @author week solider
 * @since 2020-07-29
 */
@Slf4j
public abstract class AbstractBootstrapServer implements BootstrapServer {

    protected final InitializeNettyProperties properties;

    protected final NettyHandlerProperties handlerProperties;

    /**
     * 处理客户端的连接
     * (接收到 web 的请求后，会创建一个 channel，而后会将 channel 传递给 work group 即{@link #workGroup})
     */
    protected EventLoopGroup bossGroup;

    /**
     * 处理客户端的消息
     * (接收到 boss group 即 {@link #bossGroup} 的 channel 后对消息的读写)
     */
    protected EventLoopGroup workGroup;

    protected ServerBootstrap bootstrap;

    public AbstractBootstrapServer(InitializeNettyProperties properties,
                                   NettyHandlerProperties handlerProperties) {

        this.properties = properties;
        this.handlerProperties = handlerProperties;
    }

    /**
     * 先进行了初始化工作，其中甄别了是否处在 {@code linux}操作系统上，如果是采用的是{@code linux epoll}的方式处理连接
     * 而且对启动参数提供了尽可能多的选项 {@link InitializeNettyProperties} 可查看对参数详细的说明，参数都是在配置文件
     * 中可配置，最后绑定端口以及添加了 {@code netty} 自带的监听器 {@link io.netty.channel.ChannelFutureListener} 帮
     * 助启动及关闭监听是否成功等。
     */
    @Override
    public void start() {
        initEventPool();
        initChannelOption();
        started();
    }

    protected abstract void started();

    /**
     * 关闭 {@code netty} 服务器，使用异步的方式优雅关机
     */
    @Override
    public void shutdown() {
        closed();
    }

    protected abstract void closed();

    /**
     * 初始化服务器
     */
    private void initEventPool() {
        bootstrap = new ServerBootstrap();
        if (useEpoll())
            createEpollServer();
        else
            createNioServer();
    }

    private void initChannelOption(){
        bootstrap.group(bossGroup, workGroup)
                .channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
                .option(ChannelOption.SO_REUSEADDR, properties.isReuseaddr())
                .option(ChannelOption.SO_BACKLOG, properties.getBacklog())
                .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .option(ChannelOption.SO_RCVBUF, properties.getRcvBuf())
                .childHandler(getChannelInitializer())
                .childOption(ChannelOption.TCP_NODELAY, properties.isNodeLay())
                .childOption(ChannelOption.SO_KEEPALIVE, properties.isKeepalive())
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
    }

    protected abstract ChannelInitializer<SocketChannel> getChannelInitializer();

    /**
     * 使用 epoll 的方式
     */
    private void createEpollServer() {
        if (Objects.isNull(bossGroup)) {
            bossGroup = new EpollEventLoopGroup(handlerProperties.getBossThreadSize(),
                    new SimpleThreadFactory(SimpleThreadFactory.LINUX_BOSS));
        }

        if (Objects.isNull(workGroup)) {
            workGroup = new EpollEventLoopGroup(handlerProperties.getWorkThreadSize(),
                    new SimpleThreadFactory(SimpleThreadFactory.LINUX_WORK));
        }
    }

    /**
     * 使用 NIO 的方式
     */
    private void createNioServer() {
        if (Objects.isNull(bossGroup)) {
            bossGroup = new NioEventLoopGroup(handlerProperties.getBossThreadSize(),
                    new SimpleThreadFactory(SimpleThreadFactory.BOSS));
        }

        if (Objects.isNull(workGroup)) {
            workGroup = new NioEventLoopGroup(handlerProperties.getWorkThreadSize(),
                    new SimpleThreadFactory(SimpleThreadFactory.WORK));
        }
    }

    /**
     * 是否处在 linux 操作系统上，并且可用
     *
     * @return is true of linux platform
     */
    protected boolean useEpoll() {
        return RemoteUtil.isLinuxPlatform() && Epoll.isAvailable();
    }
}
